/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "DecompressorPVR.h"
#include <Gluck.h>
#include <Endianess.h>

namespace liba
{
namespace pictureformat
{

#define PVR_TEXTURE_FLAG_TYPE_MASK	0xff
	
static char gPVRTexIdentifier[4] = {'P', 'V', 'R', '!' };
	
enum
{
	kPVRTextureFlagTypePVRTC_2 = 24,
	kPVRTextureFlagTypePVRTC_4
};
	
typedef struct _PVRTexHeader
	{
		uint32_t headerLength;
		uint32_t height;
		uint32_t width;
		uint32_t numMipmaps;
		uint32_t flags;
		uint32_t dataLength;
		uint32_t bpp;
		uint32_t bitmaskRed;
		uint32_t bitmaskGreen;
		uint32_t bitmaskBlue;
		uint32_t bitmaskAlpha;
		uint32_t pvrTag;
		uint32_t numSurfs;
} PVRTexHeader;

	
DecompressorPVR::DecompressorPVR( const void * data, int size )
:	width( 0 ),
	height( 0 ),
	scan_line( 0 ),
	frame(0)
{
	PVRTexHeader * header = NULL;
	uint32_t flags, pvrTag;
	uint32_t dataLength = 0, dataOffset = 0, dataSize = 0;
	uint32_t blockSize = 0, widthBlocks = 0, heightBlocks = 0;
	uint32_t curr_width = 0, curr_height = 0, bpp = 4;
	uint8_t *bytes = NULL;
	uint32_t formatFlags;
	
	header = (PVRTexHeader *)data;
	
	pvrTag = endianess::get_type_le(header->pvrTag);
	
	if (gPVRTexIdentifier[0] != ((pvrTag >>  0) & 0xff) ||
		gPVRTexIdentifier[1] != ((pvrTag >>  8) & 0xff) ||
		gPVRTexIdentifier[2] != ((pvrTag >> 16) & 0xff) ||
		gPVRTexIdentifier[3] != ((pvrTag >> 24) & 0xff))
	{
		return;
	}
	
	flags = endianess::get_type_le(header->flags);
	formatFlags = flags & PVR_TEXTURE_FLAG_TYPE_MASK;
	
	//console tool gives different format flag for PVRTC4
	if(formatFlags == 13)
		formatFlags = kPVRTextureFlagTypePVRTC_4;
	
	if (formatFlags == kPVRTextureFlagTypePVRTC_4 || formatFlags == kPVRTextureFlagTypePVRTC_2)
	{
		if (formatFlags == kPVRTextureFlagTypePVRTC_4)
			format = PVR4;
		else if (formatFlags == kPVRTextureFlagTypePVRTC_2)
			format = PVR2;
		
		curr_width = width = endianess::get_type_le(header->width);
		curr_height = height = endianess::get_type_le(header->height);
		
		if (endianess::get_type_le(header->bitmaskAlpha))
		{
			if(format == PVR4)
				format = PVR4A;
			else
				format = PVR2A;
		}
		
		dataLength = endianess::get_type_le(header->dataLength);
		
		bytes = ((uint8_t *)data) + sizeof(PVRTexHeader);
		
		// Calculate the data size for each texture level and respect the minimum number of blocks
		while (dataOffset < dataLength)
		{
			if (formatFlags == kPVRTextureFlagTypePVRTC_4)
			{
				blockSize = 4 * 4; // Pixel by pixel block size for 4bpp
				widthBlocks = curr_width / 4;
				heightBlocks = curr_height / 4;
				bpp = 4;
			}
			else
			{
				blockSize = 8 * 4; // Pixel by pixel block size for 2bpp
				widthBlocks = curr_width / 8;
				heightBlocks = curr_height / 4;
				bpp = 2;
			}
			
			// Clamp to minimum number of blocks
			if (widthBlocks < 2)
				widthBlocks = 2;
			if (heightBlocks < 2)
				heightBlocks = 2;
			
			dataSize = widthBlocks * heightBlocks * ((blockSize  * bpp) / 8);

			uint8_t * frame_data = new uint8_t[dataSize];
			memcpy(frame_data, ((uint8_t *)bytes) + dataOffset, dataSize);
			mipmaps_data.push_back(frame_data);
			
			dataOffset += dataSize;
			
			curr_width = std::max<uint32_t>(curr_width >> 1, 1);
			curr_height = std::max<uint32_t>(curr_height >> 1, 1);
		}
	}
}

DecompressorPVR::~DecompressorPVR()
{
	for(int i = 0; i < mipmaps_data.size(); ++i)
	{
		delete mipmaps_data[i]; mipmaps_data[i] = 0;
	}
}

void DecompressorPVR::unpack_scanline( void * dst )
{
	int stride = 0;
	if(format == PVR4 || format == PVR4A)
		stride = (width >> frame) / 2;
	else
		stride = (width >> frame) / 4;
	
	if(stride * (height >> frame) < 32)
	{
		stride = 32/(height >> frame);
	}
	
	memcpy(dst, mipmaps_data[frame] + scan_line*stride, stride);
	scan_line++;
}

void DecompressorPVR::skip_scanline(int count)
{
	scan_line += count;
}

} // namespace pictureformat
} // namespace liba

